package com.ea.futtradewatch;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;

import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.ClientResponse.Status;
import com.sun.jersey.api.client.WebResource;

public class FutTradeWatcher
{
    private static final long RESOURCE_ID_PLAYER_CONTRACT = 1347178283;
    private static final long RESOURCE_ID_RARE_PLAYER_CONTRACT = 1347178286;
	private static final long MIN_CREDITS_TO_CONTINUE_BUYING = 30000;
	
    private static boolean doLogin = true;
    private static String COOKIE = "s_nr1=1351668972394-REPEAT; __utma=119042845.2010618935.1348684556.1351626388.1351668968.75; __utmz=119042845.1348684556.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); FUTWebPhishing433696670=3831721934880669502; FUTWebPhishing433696673=8208457708707538270; FUTWebPhishing880897245=6067885114705022825; FUTWebPhishing433696671=3832418466378815887; s_sivo=INTL%3AEASFW%3ANONE; s_ria=flash%2011%7Csilverlight%204.1; __utmc=119042845; s_ppv=100; s_cc=true; s_sq=%5B%5BB%5D%5D; s_pv=GLOBAL%3AUK%3ASPORTS%3AEAC%3AWIDGET%3AFIFA%3AEASFW%3AFUT%3ASECURITYQUESTIONCHALLENGE; __utmb=119042845.1.10.1351668968";
    private static String SID = "41758f5b-6f21-4061-bf2b-cdc62491deb0";
    
    private static final int MAX_BUY_NOW_TO_BUY = 400;
    // description, resource id, # to have in club, # already in club, URL to buy, URL to find how many in club (put 0 if you're sure you have none)
    private static Object[][] resToReplenish = {
//    	{"formation 3412",	1347180323L, 5, 0, "https://utas.s2.fut.ea.com/ut/game/fifa13/auctionhouse?maxb=200&type=training&num=80&cat=playerFormation&blank=10&start=0",
//    		"https://utas.s2.fut.ea.com/ut/game/fifa13/club?type=123&level=3&start=0&count=75"},
       	{"manager contract rare",	1347178292L, 1, 0, "https://utas.s2.fut.ea.com/ut/game/fifa13/auctionhouse?maxb=400&type=development&lev=gold&start=0&blank=10&cat=contract&num=80",
    		"https://utas.s2.fut.ea.com/ut/game/fifa13/club?type=122&level=3&start=0&count=75"},
//       	{"position CAM CM",	1347180352L, 1, 0, "https://utas.s2.fut.ea.com/ut/game/fifa13/auctionhouse?blank=10&lev=gold&type=training&start=0&num=100&cat=position&maxb=2000",
//    		"https://utas.s2.fut.ea.com/ut/game/fifa13/club?start=0&level=3&count=75&type=123"},
       	{"formation 352",	1347180326L, 5, 0, "https://utas.s2.fut.ea.com/ut/game/fifa13/auctionhouse?maxb=200&type=training&num=100&cat=playerFormation&blank=10&start=0",
    		"https://utas.s2.fut.ea.com/ut/game/fifa13/club?type=123&level=3&start=0&count=75"},
       	{"formation 41212",	1347180327L, 3, 0, "https://utas.s2.fut.ea.com/ut/game/fifa13/auctionhouse?num=96&cat=playerFormation&start=0&minb=2000&maxb=7000&type=training&blank=10",
       		"https://utas.s2.fut.ea.com/ut/game/fifa13/club?type=123&level=3&start=0&count=75"},
//       	{"Benzema",	1342342433L, 1, 0, "https://utas.s2.fut.ea.com/ut/game/fifa13/auctionhouse?nat=18&type=player&leag=53&start=0&team=243&maxb=75000&num=96&pos=ST", 0},
//       	{"fitness team bronze",	1347179284L, 2, 0, "https://utas.s2.fut.ea.com/ut/game/fifa13/auctionhouse?maxb=400&type=development&start=0&blank=10&cat=fitness&num=96&minb=250&lev=bronze",
//    		"https://utas.s2.fut.ea.com/ut/game/fifa13/club?type=121&level=1&start=0&count=14"},
//       	{"silver contract",	RESOURCE_ID_SILVER_PLAYER_CONTRACT, 5, 0, "https://utas.s2.fut.ea.com/ut/game/fifa13/auctionhouse?cat=contract&lev=silver&blank=10&type=development&start=0&num=64&maxb=350",
//    		"https://utas.s2.fut.ea.com/ut/game/fifa13/club?start=0&level=2&count=50&type=122"},
//       	{"gold pace",	RESOURCE_ID_PACE_GOLD, 5, 0, "https://utas.s2.fut.ea.com/ut/game/fifa13/auctionhouse?cat=playerTraining&blank=10&type=training&start=0&lev=gold&num=100&maxb=300",
//    		"https://utas.s2.fut.ea.com/ut/game/fifa13/club?start=0&level=3&count=50&type=123"},
    };    
    
    // resource id, description, URL for market price, URL for buying, current market price (buy now - also relist price), current start price to relist, max price to buy ever
    private static Map<Long, Object[]> resToResell = new HashMap<Long, Object[]>();
    static {
    	resToResell.put(1347180327L, 	new Object[]{"formation 41212", "https://utas.s2.fut.ea.com/ut/game/fifa13/auctionhouse?num=96&cat=playerFormation&start=0&minb=4000&type=training&blank=10",
    			"https://utas.s2.fut.ea.com/ut/game/fifa13/auctionhouse?num=96&cat=playerFormation&start=0&minb=2000&maxb=%d&type=training&blank=10", (long)0, (long)0, (long)8000});
    	resToResell.put(1347180332L, 	new Object[]{"formation 433", "https://utas.s2.fut.ea.com/ut/game/fifa13/auctionhouse?num=96&cat=playerFormation&start=0&minb=2000&maxb=10000&type=training&blank=10",
    			"https://utas.s2.fut.ea.com/ut/game/fifa13/auctionhouse?num=96&cat=playerFormation&start=0&minb=2000&maxb=%d&type=training&blank=10", (long)0, (long)0, (long)3000});
    	resToResell.put(1347179284L, 	new Object[]{"fitness bronze", "https://utas.s2.fut.ea.com/ut/game/fifa13/auctionhouse?num=64&lev=bronze&blank=10&type=development&start=0&cat=fitness&minb=300",
    			"https://utas.s2.fut.ea.com/ut/game/fifa13/auctionhouse?num=96&lev=bronze&blank=10&type=development&start=0&cat=fitness&minb=300&maxb=%d", (long)0, (long)0, (long)800});
//    	resToResell.put(RESOURCE_ID_FITNESS_TEAM_GOLD, 	new Object[]{"fitness gold", "https://utas.s2.fut.ea.com/ut/game/fifa13/auctionhouse?num=64&lev=gold&blank=10&type=development&start=0&cat=fitness&minb=1000&maxb=3000",
//    			"https://utas.s2.fut.ea.com/ut/game/fifa13/auctionhouse?num=16&lev=gold&blank=10&type=development&start=0&cat=fitness&minb=500&maxb=%d", (long)0, (long)0, (long)1400});
//    	resources.put(RESOURCE_ID_FITNESS_TEAM_SILVER, 	new Object[]{"fitness bronze", "https://utas.s2.fut.ea.com/ut/game/fifa13/auctionhouse?num=16&lev=silver&blank=10&type=development&start=0&cat=fitness&minb=1000&maxb=3000",
//    			"https://utas.s2.fut.ea.com/ut/game/fifa13/auctionhouse?lev=silver&blank=10&type=development&start=0&cat=fitness&num=16&maxb=2000", (long)0, (long)0, (long)1500});
//    	resources.put(RESOURCE_ID_FORMATION_4222, 	new Object[]{"formation 4222", "https://utas.s2.fut.ea.com/ut/game/fifa13/auctionhouse?num=64&cat=playerFormation&start=0&minb=10000&type=training&blank=10",
//    			"https://utas.s2.fut.ea.com/ut/game/fifa13/auctionhouse?num=64&cat=playerFormation&start=0&minb=30000&maxb=%d&type=training&blank=10", (long)0, (long)0});
    }
//    private static final int MIN_DESIRED_PROFIT = 3000;
    private static final int MIN_DESIRED_PROFIT_PERCENT = 30;
//    private static int currentDesiredProfit = MIN_DESIRED_PROFIT;
//    private static final int[][] bidThresholds = { {25, MIN_DESIRED_PROFIT}, {15, MIN_DESIRED_PROFIT+1000}, {7, MIN_DESIRED_PROFIT+2000}, {4, MIN_DESIRED_PROFIT+4000}, {3, MIN_DESIRED_PROFIT+8000}};
    // if # of bid tokens in [10, 6) only buy contracts at 400 and below
//    private static final int[][] bidThresholds = { {25, MAX_BUY_NOW_TO_BUY}, {10, 350}, {8, 300}, {6, 250}, {5, 200} };
    private static int currentMaxBuyNowToBuy = 300, currentMaxBuyNowToBuyRare = 500;  

    private static Client client = new Client();
    private static final int POLL_INTERVAL = 5;    // in seconds
//    private static String COOKIE_TEMPL = "__utma=103303007.1091356809.1334239718.1337855402.1337879296.44; __utmz=103303007.1334239718.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); s_nr1=1337879311854-REPEAT; FUTWebPhishing880897245=8398646138354063698; easf_sess_com=%s; _chartbeat2=koswpigf22g4f2pn.1335796532864.1337879314015.00000000000001; EASW_KEY=%s; s_sivo=%3AEASFW%3ANONE; s_ria=flash%2011%7Csilverlight%204.1; __utmc=103303007; s_cc=true; s_sq=eaeacomdev%3D%2526pid%253D%25253A%25253ASPORTS%25253AEAC%25253AMKT%25253ANONE%25253AEASFW%25253ANONE%25253A%2526pidt%253D1%2526oid%253DContinue%2526oidt%253D3%2526ot%253DSUBMIT; s_ppv=100; FUTWebPhishing1538010869=4688286807984380240; s_pv=NA%3AUS%3ASPORTS%3AEAC%3AMKT%3AFIFA%3AEASFW%3AFUT%3ALANDINGPAGE; __utmb=103303007.3.10.133787";
    private static final int MIN_BUY_NOW = 200;
    private static final int MAX_BUY_NOW = 800;
    private static final int CARDSUBTYPEID_PLAYER_CONTRACT = 201;
    private static SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private static int nbContractsBought = 0;
//    private static List<LongPair> boughtItemIds = new ArrayList<LongPair>();
    private static long credits = Long.MAX_VALUE;
//    private static long bidTokens = 1;
	private static List<Long> closedTradeIds;
	private static List<LongPair> expiredItemIds;
    private static long privateStashSize = 0;
    private static final long PRIVATE_STASH_DESIRED_SIZE = 10;
    private static final int IDX_RES_NAME = 0;
    private static final int IDX_RES_ID = 1;
    private static final int IDX_RES_NB_TO_BUY = 2;
    private static final int IDX_RES_NB_BIN_CLUB = 3;
    private static final int IDX_RES_URL_TO_BUY = 4;
    private static final int IDX_RES_URL_NB_IN_CLUB = 5;
    private static int tradePileSize = 0;
    private static final int TRADEPILE_MAX_SIZE = 40;
    private static final int TRADEPILE_MAX_SIZE_FOR_CONTRACTS = 38;
    private static List<Integer> lastMarketPrices = new ArrayList<Integer>();

    private static final int IDX_DESC = 0;
    private static final int IDX_URL_MARKET_PRICE = 1;
    private static final int IDX_URL_TO_BUY = 2;
    private static final int IDX_MARKET_PRICE = 3;
    private static final int IDX_START_PRICE = 4;
    private static final int IDX_MAX_EVER = 5;

    private static int currentBuyNowToRelist = 500;
    // counting up from 200 buy now, first level that has at least MIN_CONTRACTS_MARKET_PRICE contracts for sale is giving the market price 
    private static int MIN_CONTRACTS_MARKET_PRICE = 10;

    // resourceId, relist starting price, relist buy now price
    private static long[][] relistPricesContracts = {
    	{RESOURCE_ID_PLAYER_CONTRACT, currentBuyNowToRelist - 50, currentBuyNowToRelist},
    	{RESOURCE_ID_RARE_PLAYER_CONTRACT, 2*currentBuyNowToRelist - 200, 2*currentBuyNowToRelist - 100},
    };
	private static boolean loggedIn = false;

    /**
     * @param args
     * @throws ParseException 
     * @throws InterruptedException 
     */
    public static void main(String[] args) throws Exception
    {
        int iterations = 0;
		int interval = 60000;
    	while (true) {
    		try {
    	       	if (doLogin && loggedIn == false) {
//					log("Not logged in!");
					SID = null;
					while (SID == null) {
						if (!login())
							log("Failed to log in!");
						else {
//							log("Logged back in!");
							loggedIn = true;
						}
					}
				}
				if (iterations % 12 == 0) {
					privateStashSize = getPrivateStashSize();
					cleanupTradePile();
					updateContractsMarketPrice();
//					updateBidTokens();
					updateResMarketPrice();
					relistExpiredResources();
					replenishResources();
					iterations = 0;
				}
				updateMaxBuyNowsToBuy();
				searchAndBuyContracts();
				searchAndBuyResources();
				relistBoughtResources();
				iterations++;
				Thread.sleep(POLL_INTERVAL * 1000);
			} catch (Exception e) {
				if (e.getMessage() != null) {
					if (doLogin && e.getMessage().contains("401")) {
						loggedIn = false;
//						Thread.sleep(POLL_INTERVAL * 1000);
//						Thread.sleep(interval);
					}
				}
				else
					e.printStackTrace();
			}
        }
    }
    
    private static void updateMaxBuyNowsToBuy() {
		int newCurrentMaxBuyNowToBuy = MAX_BUY_NOW_TO_BUY;
		int thrFreeSlotsInTradePile = TRADEPILE_MAX_SIZE_FOR_CONTRACTS / 2;
		int freeSlotsInTradepile = TRADEPILE_MAX_SIZE_FOR_CONTRACTS - tradePileSize;
		while (newCurrentMaxBuyNowToBuy > MIN_BUY_NOW && thrFreeSlotsInTradePile > freeSlotsInTradepile) {
			newCurrentMaxBuyNowToBuy -= 50;
			thrFreeSlotsInTradePile /= 2;
		}
		newCurrentMaxBuyNowToBuy = Math.min(newCurrentMaxBuyNowToBuy, currentBuyNowToRelist - 100);
//		if (newCurrentMaxBuyNowToBuy != currentMaxBuyNowToBuy)
//			log("Now buying contracts at max " + newCurrentMaxBuyNowToBuy);
		currentMaxBuyNowToBuy = newCurrentMaxBuyNowToBuy;
		currentMaxBuyNowToBuyRare = 2*currentMaxBuyNowToBuy - 200;
	}

	// see which of the x (12) last market prices occurs the most and go with that one
    private static void updateContractsMarketPrice() {
    	if (lastMarketPrices.isEmpty())
    		return;
    	
    	Map<Integer, Integer> priceOccs = new HashMap<Integer, Integer>();
    	for (Integer price : lastMarketPrices)
    		if (!priceOccs.containsKey(price)) {
    			priceOccs.put(price, 1);
    		}
    		else {
    			int nbOcc = priceOccs.get(price);
    			priceOccs.put(price, nbOcc+1);
    		}
    	
    	int priceMaxOcc = Integer.MAX_VALUE;
    	int maxOcc = 0;
    	for (Integer price : priceOccs.keySet())
    		if (maxOcc < priceOccs.get(price)) {
    			priceMaxOcc = price;
    			maxOcc = priceOccs.get(price);
    		}
    		
    	if (priceMaxOcc != currentBuyNowToRelist) {
	    	currentBuyNowToRelist = priceMaxOcc;
	    	updateRelistPricesContracts();
//	    	log("Now relisting contracts at\t" + currentBuyNowToRelist);
    	}
    	
    	lastMarketPrices.clear();
	}

	private static void updateRelistPricesContracts() {
    	for (long[] tuple : relistPricesContracts) {
    		if (tuple[0] == RESOURCE_ID_PLAYER_CONTRACT) {
    	    	tuple[1] = currentBuyNowToRelist - 50;
    	    	tuple[2] = currentBuyNowToRelist;
    		}
    		if (tuple[0] == RESOURCE_ID_RARE_PLAYER_CONTRACT) {
    	    	tuple[1] = 2*currentBuyNowToRelist - 100;
    	    	tuple[2] = 2*currentBuyNowToRelist;
//    	    	tuple[1] = getValidPrice(2*currentBuyNowToRelist - 50);
//    	    	tuple[2] = getValidPrice(2*currentBuyNowToRelist - 100);
    		}
    	}    	
    }

    private static void updateResMarketPrice() throws Exception {
		for (Map.Entry<Long, Object[]> res : resToResell.entrySet()) {
			Long resourceId = res.getKey();
			Object[] tuple = res.getValue();
	        WebResource webResource = client.resource((String)tuple[IDX_URL_MARKET_PRICE]);
	        ClientResponse response = webResource.
	                        accept(MediaType.APPLICATION_JSON_TYPE).
	                        header("X-UT-SID", SID).
	                        header("Cookie", COOKIE).
	                        get(ClientResponse.class);
	        	        
	        JSONObject json = checkResponse(response, "updating resource market price");
	        JSONArray auctionInfo = (JSONArray)json.get("auctionInfo");
//	        TreeSet<Long> prices = new TreeSet<Long>();
	        List<Long> prices = new ArrayList<Long>();
	        for (int i = 0; i < auctionInfo.size(); i++) {
	            JSONObject auction = (JSONObject)auctionInfo.get(i);
	            JSONObject itemData = (JSONObject)auction.get("itemData");
	            long auctionResId = (Long)itemData.get("resourceId");
	            long buyNowPrice = (Long)auction.get("buyNowPrice");
	            if (resourceId == auctionResId && buyNowPrice > 0) {
	            	prices.add(buyNowPrice);
	            }
	        }
	        long marketPrice = getMarketPrice(prices);
	        if (marketPrice < 0)
	        	continue;
//	        if (marketPrice != (Long)tuple[IDX_MARKET_PRICE])
//	        	log("Market price " + (String)tuple[IDX_DESC] + "\t" + marketPrice);
	        tuple[IDX_MARKET_PRICE] = marketPrice;
	        long startPrice = getValidPrice(marketPrice - 50);
	        tuple[IDX_START_PRICE] = startPrice;
		}
	}

	private static Long getMarketPrice(List<Long> prices) {
		DescriptiveStatistics stats = new DescriptiveStatistics();
		for (long value : prices)
			stats.addValue(value);
		double mean = stats.getMean();
		double std = stats.getStandardDeviation();
		
		// eliminate values outside of the standard deviation
		stats = new DescriptiveStatistics();
		for (long value : prices)
			if (mean - std < value && value < mean + std)
				stats.addValue(value);		
		long avg = (long)(stats.getMean() * 0.95);
		
//		Collections.sort(prices);
//		final int desIndex = 1;
//		for (int i = 0; i < desIndex; i++)
//			if (prices.size() > i+1)
//				prices.remove(i);
//		if (prices.size() > 1)
//			return prices.get(0) - 100;
//		return (long)-1;
				
//		prices.pollFirst();
//		int i = 0;
//		long avg = 0;
//		for (Iterator<Long> it = prices.iterator(); it.hasNext() && i<3; i++) {
//			long price = it.next();
//			avg = (avg*i + price)/(i+1);
//		}
		return getValidPrice(avg);
	}

    private static void searchAndBuyResources() throws Exception {
		for (Map.Entry<Long, Object[]> res : resToResell.entrySet()) {
			Long resourceId = res.getKey();
			Object[] tuple = res.getValue();
			if ((Long)tuple[IDX_MARKET_PRICE] <= 0)
				continue;
			long maxPriceToBuy = // Math.min((Long)tuple[IDX_MARKET_PRICE] - currentDesiredProfit,
					(Long)tuple[IDX_MARKET_PRICE] * (100 - MIN_DESIRED_PROFIT_PERCENT)/100;
			String url = String.format((String)tuple[IDX_URL_TO_BUY], maxPriceToBuy);
	        WebResource webResource = client.resource(url);
	        ClientResponse response = webResource.
	                        accept(MediaType.APPLICATION_JSON_TYPE).
	                        header("X-UT-SID", SID).
	                        header("Cookie", COOKIE).
	                        get(ClientResponse.class);
	        
	        JSONObject json = checkResponse(response, "buying resources");
	        JSONArray auctionInfo = (JSONArray)json.get("auctionInfo");
	        if (auctionInfo != null)
		        for (int i = 0; i < auctionInfo.size(); i++) {
		            JSONObject auction = (JSONObject)auctionInfo.get(i);
		            JSONObject itemData = (JSONObject)auction.get("itemData");
		            long auctionResId = (Long)itemData.get("resourceId");
		            long buyNowPrice = (Long)auction.get("buyNowPrice");
		            if (resourceId == auctionResId && buyNowPrice > 0 && buyNowPrice <= (Long)tuple[IDX_MAX_EVER]) {
	            		long tradeId = (Long)auction.get("tradeId");
	            		long itemId = 0;
	            		if ((itemId = buyItem(tradeId, buyNowPrice)) > 0) {
	            			log("Bought " + tuple[IDX_DESC] + "\t" + buyNowPrice + "\t" + ((Long)tuple[IDX_MARKET_PRICE] - buyNowPrice));
//	                       	boughtItemIds.add(new LongPair(itemId, resourceId));
	            		}
//	            		else 
//	            			log("Passed " + tuple[IDX_DESC] + "\t" + buyNowPrice);
		            }
		        }
		}
	}

	/**
     * In the returning pair, itemId is relist start price, and resourceId is relist buy now price
     * @param resourceId
     * @return
     */
    private static LongPair getRelistPrice(long resourceId) {
    	Object[] tuple = resToResell.get(resourceId);
    	if (tuple != null && (Long)tuple[IDX_MARKET_PRICE] > 0) {
    		// make sure to never lose money while relisting resources
	        long marge = Math.round((Long)tuple[IDX_MAX_EVER] * 0.05 / 100 * 100);
	        long startPrice = Math.max(getValidPrice((Long)tuple[IDX_MARKET_PRICE] - 50), getValidPrice((Long)tuple[IDX_MAX_EVER] + marge));	        
	        long buyNowPrice = startPrice, increment = 50;
	        while (buyNowPrice == startPrice) {
	        	buyNowPrice = getValidPrice(buyNowPrice + increment);
	        	increment += 50;
	        }
	        tuple[IDX_START_PRICE] = startPrice;
	        tuple[IDX_MARKET_PRICE] = buyNowPrice;  
	        return new LongPair((Long)tuple[IDX_START_PRICE], (Long)tuple[IDX_MARKET_PRICE]);
    	}

    	for (long[] longTuple : relistPricesContracts) {
    		if (longTuple[0] == resourceId)
    			return new LongPair(longTuple[1], longTuple[2]);
    	}
    	
    	return null;
    }
    
    private static void replenishResources() throws Exception {
    	updateNbResToReplenish();
		for (Object[] res : resToReplenish) {
	        WebResource webResource = client.resource((String)res[IDX_RES_URL_TO_BUY]);
	        ClientResponse response = webResource.
	                        accept(MediaType.APPLICATION_JSON_TYPE).
	                        header("X-UT-SID", SID).
	                        header("Cookie", COOKIE).
	                        get(ClientResponse.class);
	        
	        JSONObject json = checkResponse(response, "replenishing resources");
	        JSONArray auctionInfo = (JSONArray)json.get("auctionInfo");
	        for (int i = 0; i < auctionInfo.size(); i++) {
	            JSONObject auction = (JSONObject)auctionInfo.get(i);
	            JSONObject itemData = (JSONObject)auction.get("itemData");
	            long resourceId = (Long)itemData.get("resourceId");
	            long buyNowPrice = (Long)auction.get("buyNowPrice");
	            if (resourceId == (Long)res[IDX_RES_ID] && buyNowPrice > 0) {
//	            	String preferredPosition = (String)itemData.get("preferredPosition");
//	            	String formation = (String)itemData.get("formation");
//	            	log((String)res[IDX_RES_NAME] + "\t" + preferredPosition + "\t" + formation + "\t" + buyNowPrice);
	            	if ((Integer)res[IDX_RES_NB_TO_BUY] < 0 || (Integer)res[IDX_RES_NB_TO_BUY] > (Integer)res[IDX_RES_NB_BIN_CLUB]) {
	            		long tradeId = (Long)auction.get("tradeId");
	            		long itemId = 0;
	            		if ((itemId = buyItem(tradeId, buyNowPrice)) > 0) {
	            			log("Bought " + res[IDX_RES_NAME] + "\t" + buyNowPrice);
	            			res[IDX_RES_NB_BIN_CLUB] = ((Integer)res[IDX_RES_NB_BIN_CLUB]) + 1;
//	            			if (isResellable(resourceId))
//	                        	boughtItemIds.add(new LongPair(itemId, resourceId));
//	            			else
	            				sendToClub(itemId);
	            		}
	            	}
	            }
	        }
		}
	}

	private static void updateNbResToReplenish() throws Exception {
		for (Object[] res : resToReplenish) {
			if (! (res[IDX_RES_URL_NB_IN_CLUB] instanceof String))
				continue;
			res[IDX_RES_NB_BIN_CLUB] = 0;
			WebResource webResource = client.resource((String)res[IDX_RES_URL_NB_IN_CLUB]);
	        ClientResponse response = webResource.
	                        accept(MediaType.APPLICATION_JSON_TYPE).
	                        header("X-UT-SID", SID).
	                        header("Cookie", COOKIE).
	                        get(ClientResponse.class);
	        
	        JSONObject json = checkResponse(response, "updating nb resources to replenish");
	        JSONArray itemData = (JSONArray)json.get("itemData");
	        for (int i = 0; i < itemData.size(); i++) {
	            JSONObject item = (JSONObject)itemData.get(i);
	            long resourceId = (Long)item.get("resourceId");
	            if (resourceId == (Long)res[IDX_RES_ID])
	            	res[IDX_RES_NB_BIN_CLUB] = (Integer)res[IDX_RES_NB_BIN_CLUB] + 1;
	        }
		}
	}

//	private static void updateBidTokens() throws Exception {
//		String url = "https://utas.s2.fut.ea.com/ut/game/fifa13/user/bidTokens";
//        WebResource webResource = client.resource(url);
//        ClientResponse response = webResource.
//                header("Cookie", COOKIE).
//                header("X-UT-SID", SID).
//                get(ClientResponse.class);
//        String output = response.getEntity(String.class);
//        JSONObject json = (JSONObject)(new JSONParser().parse(output));
//        bidTokens = (Long)json.get("count");
        
//        updateCurrentDesiredProfit();
//        updateCurrentMaxBuyNow();
//	}

//	private static void updateCurrentMaxBuyNow() {
//		// see what is the maximum price I should pay now according to thresholds
////		int oldMax = currentMaxBuyNowToBuy;
//        currentMaxBuyNowToBuy = MAX_BUY_NOW_TO_BUY;
//        for (int[] i : bidThresholds) {
//        	if (bidTokens <= i[0])
//        		currentMaxBuyNowToBuy = i[1];
//        	else
//        		break;
//        }
//        if (currentMaxBuyNowToBuy != oldMax)
//        	log("Now buying contracts at max\t" + currentMaxBuyNowToBuy + "\t" + bidTokens);
//	}

//	private static void updateCurrentDesiredProfit() {
//		// see what is the maximum price I should pay now according to
//		// thresholds
//		int oldMax = currentDesiredProfit;
//		currentDesiredProfit = MAX_BUY_NOW_TO_BUY;
//		for (int[] i : bidThresholds) {
//			if (bidTokens <= i[0])
//				currentDesiredProfit = i[1];
//			else
//				break;
//		}
//		if (currentDesiredProfit != oldMax)
//			log("Now looking min profit\t" + currentDesiredProfit + "\t"
//					+ bidTokens);
//	}
//	
	// number of contracts in club
    private static long getPrivateStashSize() throws Exception {
		String url = "https://utas.s2.fut.ea.com/ut/game/fifa13/club?level=3&count=16&type=122&start=0";
        WebResource webResource = client.resource(url);
        ClientResponse response = webResource.
                header("Cookie", COOKIE).
                header("X-UT-SID", SID).
                get(ClientResponse.class);	
        JSONObject json = checkResponse(response, "getting private stash size");
        JSONArray itemData = (JSONArray)json.get("itemData");
	    if (itemData == null) {
	    	return -1;
	    }
        
        return itemData.size();
	}

	private static void cleanupTradePile() throws Exception {
		final String urlTempl = "https://utas.s2.fut.ea.com/ut/delete/game/ut12/trade/%d";
		updateClosedAndExpiredTradeIds();
		for (long id : closedTradeIds) {
			String url = String.format(urlTempl, id);
	        WebResource webResource = client.resource(url);
	        ClientResponse response = webResource.
                    header("Cookie", COOKIE).
                    header("X-UT-SID", SID).
                    get(ClientResponse.class);	
	        checkResponse(response, "cleaning up trade pile");
		}
	}

	private static void updateClosedAndExpiredTradeIds() throws Exception {
		String url = "https://utas.s2.fut.ea.com/ut/game/fifa13/tradepile";
        WebResource webResource = client.resource(url);
        ClientResponse response = webResource.
                header("Cookie", COOKIE).
                header("X-UT-SID", SID).
                get(ClientResponse.class);	

        JSONObject json = checkResponse(response, "updating closed and expired trades");
        JSONArray auctionInfo = (JSONArray)json.get("auctionInfo");
        closedTradeIds = new ArrayList<Long>();
        expiredItemIds = new ArrayList<LongPair>();
        if (auctionInfo == null)
            return;
        tradePileSize = 0;
        for (int i = 0; i < auctionInfo.size(); i++) {
            JSONObject auction = (JSONObject)auctionInfo.get(i);
            String tradeState = (String)auction.get("tradeState");
            JSONObject itemData = (JSONObject)auction.get("itemData");
            long resourceId = (Long)itemData.get("resourceId");
    		tradePileSize++;
            if (tradeState != null) {
            	if ((tradeState.toString()).equals("closed")) {
            		if (isResellable(resourceId)) {
	                	long tradeId = Long.parseLong(auction.get("tradeId").toString());
	                	closedTradeIds.add(tradeId);
	                	long currentBid = (Long)auction.get("currentBid");
	                	log("Item sold for\t" + currentBid);
            		}
            		tradePileSize--;
            	}
            }
            if (tradeState == null || tradeState.toString().equals("expired")) {
        		if (isResellable(resourceId)) {
                	long itemId = (Long)(itemData.get("id"));
                	expiredItemIds.add(new LongPair(itemId, resourceId));
            	}
            }
        }
        
        long newCredits = Long.parseLong(json.get("credits").toString());
        if (doLogin && newCredits != credits)
        	log("Credits: " + newCredits);
        credits = newCredits;
	}

	private static boolean isResellable(long resourceId) {
		return resToResell.containsKey(resourceId) || isContract(resourceId);
	}

	private static boolean isContract(long resourceId) {
		return resourceId == RESOURCE_ID_PLAYER_CONTRACT || resourceId == RESOURCE_ID_RARE_PLAYER_CONTRACT;
	}

	private static void relistBoughtResources() throws Exception, InterruptedException {
		List<LongPair> boughtItems = getNewCards();
		Iterator<LongPair> it = boughtItems.iterator();
		while (it.hasNext()) {
			LongPair pair = it.next();
			
			if (privateStashSize < PRIVATE_STASH_DESIRED_SIZE && isContract(pair.getResourceId()) && sendToClub(pair.getItemId())) {
				privateStashSize++;
				log("Sent contract to private stash\t" + privateStashSize);
				continue;
			}
			
			// put it in trade pile
			if (tradePileSize >= TRADEPILE_MAX_SIZE || !sendToTradePile(pair.getItemId()))
				continue;			
			Thread.sleep(500);

			// now we can put it up for auction
			listItem(pair);
		}
	}

	private static List<LongPair> getNewCards() throws Exception {
		String url = "https://utas.s2.fut.ea.com/ut/game/fifa13/purchased/items";
        WebResource webResource = client.resource(url);
        ClientResponse response = webResource.
                header("Cookie", COOKIE).
                header("X-UT-SID", SID).
                get(ClientResponse.class);	

        JSONObject json = checkResponse(response, "getting new cards");
        JSONArray itemData = (JSONArray)json.get("itemData");
        List<LongPair> boughtItems = new ArrayList<LongPair>();
        if (itemData != null)
	        for (int i = 0; i < itemData.size(); i++) {
	            JSONObject item = (JSONObject)itemData.get(i);
	            long resourceId = (Long)item.get("resourceId");
	            long itemId = (Long)item.get("id");
	            if (isResellable(resourceId))
	            	boughtItems.add(new LongPair(itemId, resourceId));
	        }
        return boughtItems;
	}
        
	private static void relistExpiredResources() throws Exception {
		Iterator<LongPair> it = expiredItemIds.iterator();
		while (it.hasNext()) {
			LongPair pair = it.next();
			if (listItem(pair) > 0)
				it.remove();
		}
	}

	private static long listItem(LongPair pair) throws Exception {
		final String url = "https://utas.s2.fut.ea.com/ut/game/fifa13/auctionhouse";
		final String contentTempl = "{\"itemData\":{\"id\":%d},\"buyNowPrice\":%d,\"startingBid\":%d,\"duration\":3600}";
		
		LongPair prices = getRelistPrice(pair.getResourceId());
		if (prices == null) // are not supposed to sell it
			return 0;
		String content = String.format(contentTempl, pair.getItemId(), prices.getResourceId(), prices.getItemId());
        WebResource webResource = client.resource(url);
        ClientResponse response = webResource.
                header("Cookie", COOKIE).
                header("X-UT-SID", SID).
                entity(content).
                post(ClientResponse.class);	

        JSONObject json = checkResponse(response, "listing item", content);
        Long tradeId = (Long)json.get("id");
        if (tradeId != null) {
        	return tradeId;
//        	log("Re-listed contract successfully\t" + id);
        }
        else {
        	return -1;
        }		
	}
	
	private static boolean sendToTradePile(long id) throws Exception {
		boolean success = sendToPile(id, "trade");
		if (success)
			tradePileSize++;
		return success;
	}

	private static boolean sendToClub(long id) throws Exception {
		return sendToPile(id, "club");
	}

	private static boolean sendToPile(long id, String pile) throws Exception {
		final String url = "https://utas.s2.fut.ea.com/ut/game/fifa13/item";
		final String contentTempl = "{\"itemData\":[{\"id\":\"%d\",\"pile\":\"%s\"}]}";
		
		String content = String.format(contentTempl, id, pile);
        WebResource webResource = client.resource(url);
        ClientResponse response = webResource.
                header("Cookie", COOKIE).
                header("X-UT-SID", SID).
                entity(content).
                put(ClientResponse.class);	

        JSONObject json = checkResponse(response, "sending to pile", content);
        JSONArray itemData = (JSONArray)json.get("itemData");
        if (itemData != null) {
        	JSONObject item = (JSONObject)itemData.get(0);
        	Boolean success = (Boolean)item.get("success"); 
        	if (success != null && success)
        			return true;
        }
        return false;
	}

	private static void searchAndBuyContracts()
			throws Exception {
		int currBuyNowPrice;
		long nbContractsForSale;
		currBuyNowPrice = MIN_BUY_NOW;
		boolean updateMarketPrice = true;
		while (currBuyNowPrice <= MAX_BUY_NOW) {
		    nbContractsForSale = findAndBuyContractsByBuyNow(currBuyNowPrice, currBuyNowPrice);
		    if (nbContractsForSale < 0) {
		    	return;
		    }
//		    if (nbContractsForSale > 0)
//		        log("Player contract\t" + currBuyNowPrice + "\t" + nbContractsForSale + "\t" + nbContractsBought);
		    
		    // update market price
		    if (updateMarketPrice && nbContractsForSale >= MIN_CONTRACTS_MARKET_PRICE && currBuyNowPrice > currentMaxBuyNowToBuy + 50) {
		    	updateMarketPrice = false;
		    	lastMarketPrices.add(currBuyNowPrice);
		    }
		    
		    currBuyNowPrice += 50;
		}
//		System.out.println();
	}

    private static long findAndBuyContractsByBuyNow(int minBuyNowPrice, int maxBuyNowPrice) throws Exception {
        String urlTempl = "https://utas.s2.fut.ea.com/ut/game/fifa13/auctionhouse?lev=gold&start=0&num=32&blank=10&minb=%d&maxb=%d&type=development&cat=contract";
    	String url = String.format(urlTempl, minBuyNowPrice, maxBuyNowPrice);
        WebResource webResource = client.resource(url);
        ClientResponse response = webResource.
                        accept(MediaType.APPLICATION_JSON_TYPE).
                        header("X-UT-SID", SID).
                        header("Cookie", COOKIE).
                        get(ClientResponse.class);
        
        JSONObject json = checkResponse(response, "searching for contracts");
        JSONArray auctionInfo = (JSONArray)json.get("auctionInfo");
        if (auctionInfo == null)
            return -1;
        int nbContractsForSale = 0;
        for (int i = 0; i < auctionInfo.size(); i++) {
            JSONObject auction = (JSONObject)auctionInfo.get(i);
            JSONObject itemData = (JSONObject)auction.get("itemData");
            long cardSubtypeId = Long.parseLong(itemData.get("cardsubtypeid").toString());
            if (cardSubtypeId == CARDSUBTYPEID_PLAYER_CONTRACT) {
                long expires = Long.parseLong(auction.get("expires").toString());
                if (expires > 0) {
                    nbContractsForSale++;
                    if (credits >= MIN_CREDITS_TO_CONTINUE_BUYING) {
                        long tradeId = Long.parseLong(auction.get("tradeId").toString());
                        long price = Long.parseLong(auction.get("buyNowPrice").toString());
                        long resourceId = (Long)itemData.get("resourceId");
                        if (tradePileSize < TRADEPILE_MAX_SIZE_FOR_CONTRACTS && ((resourceId == RESOURCE_ID_PLAYER_CONTRACT && price <= currentMaxBuyNowToBuy) || 
                        		(resourceId == RESOURCE_ID_RARE_PLAYER_CONTRACT && price <= currentMaxBuyNowToBuyRare))) {
	                        long itemId = buyItem(tradeId, price);
	                        if (itemId > 0) {
//		                        	boughtItemIds.add(new LongPair(itemId, resourceId));
	                        	nbContractsBought++;
	                        	tradePileSize++;
	                        	updateMaxBuyNowsToBuy();
	                        	String boughtWhat = (resourceId == RESOURCE_ID_RARE_PLAYER_CONTRACT ? "Bought RARE contract " : "Bought contract "); 
	                        	log(boughtWhat + "for\t" + price + "\t" + nbContractsBought);
	                        }
//                        	else
//                        		log("Pass contract TP full\t" + price);
                        }
//                        else
//                        	log("Pass contract keep BD\t" + price + "\t" + bidTokens);
                    }
                }
            }
        }
        
        return nbContractsForSale;
    }
    
    private static long buyItem(long tradeId, long price) throws Exception
    {
    	if (credits < MIN_CREDITS_TO_CONTINUE_BUYING) {
    		log("Reached credit limit, not buying");
    		return -1;
    	}
    	if (tradePileSize >= TRADEPILE_MAX_SIZE) {
//    		log("Trade pile full, not buying");
    		return -1;
    	}
        final String BUY_TRADE_URL = "https://utas.s2.fut.ea.com/ut/game/fifa13/trade/%d/bid";
        final String BID_TRADE_CONTENT = "{\"bid\":%d}";
        String url = String.format(BUY_TRADE_URL, tradeId);
        WebResource webResource = client.resource(url);
        String content = String.format(BID_TRADE_CONTENT, price);
        ClientResponse response = webResource.
                        header("Cookie", COOKIE).
                        header("X-UT-SID", SID).
                        entity(content).
                        put(ClientResponse.class);
        
        JSONObject json = checkResponse(response, "buying item", content);
        JSONArray auctionInfo = (JSONArray)json.get("auctionInfo");
        if (auctionInfo == null) {
        	String code = (String)json.get("code");
        	if (code != null && code.equals("463"))
        		log("No more BD to buy\t" + price);
//        	else
//        		log("Could not buy");
       		return -1;
        }
    	
//        JSONObject bidTokensObj = (JSONObject)json.get("bidTokens");
//        bidTokens = (Long)bidTokensObj.get("count");
//        updateCurrentDesiredProfit();
//        updateCurrentMaxBuyNow();
        
        JSONObject auction = (JSONObject)auctionInfo.get(0);
        JSONObject itemData = (JSONObject)auction.get("itemData");
        return Long.parseLong(itemData.get("id").toString());
    }

    private static void log(String message) {
        System.out.println(df.format(new Date()) + "\t" + message);
    }

    private static boolean login() {
    	WebResource webResource = client.resource("https://www.ea.com/soccer/services/authenticate/login?email=mcandea%40gmail.com&password=siPtMarius5");
    	ClientResponse response = webResource.
    		header("Content-type", "application/x-www-form-urlencoded").
    		post(ClientResponse.class);
    	String output = response.getEntity(String.class);
    	if (!output.contains("<success>1</success>"))
    		return false;
        COOKIE = getCookie(response.getHeaders().get("Set-Cookie"), new String[]{"easf_sess_com", "EASW_KEY"});
    	
    	webResource = client.resource("http://www.ea.com/p/fut/a/card-pc/l/en/s/p/ut/auth");
    	String content = "{ \"isReadOnly\": false, \"sku\": \"393A0001\", \"clientVersion\": 3, \"nuc\": 2428702952, \"nucleusPersonaId\": 379478974, \"nucleusPersonaDisplayName\": \"Emssi\", \"nucleusPersonaPlatform\": \"pc\", \"locale\": \"en-GB\", \"method\": \"idm\", \"priorityLevel\":4, \"identification\": { \"EASW-Token\": \"\" } }";
        response = webResource.
        		header("Cookie", COOKIE).
                entity(content).
                post(ClientResponse.class);
    	output = response.getEntity(String.class);
        MultivaluedMap<String, String> headers = response.getHeaders();
        SID = (String)headers.getFirst("X-UT-SID");
        if (SID == null)
        	return false;
        
    	for (int i=0; i<4; i++)
    		if (validate())
    			break;
        
    	return true;
    }

	private static boolean validate() {
		WebResource webResource;
		ClientResponse response;
		String content;
		webResource = client.resource("http://www.ea.com/p/fut/a/card-pc/l/en/s/p/ut/game/fifa13/phishing/validate");
    	content = "answer=494109bade0c15dd9b22a550aaac23b4";
    	response = webResource.
    		header("Content-type", "application/x-www-form-urlencoded").
    		header("Cookie", COOKIE).
    		header("X-UT-SID", SID).
    		entity(content).
    		post(ClientResponse.class);
    	if (response.getClientResponseStatus() != Status.OK)
    		return false;
        COOKIE = COOKIE + ";" + getCookie(response.getHeaders().get("Set-Cookie"), new String[]{"FUTWebPhishing"});
        return true;
	}

	private static String getCookie(List<String> cookieList, String[] cookieNames) {
		StringBuffer buf = new StringBuffer();
		for (String cookie : cookieList) {
			StringTokenizer st = new StringTokenizer(cookie, ";");
			while (st.hasMoreTokens()) {
				String token = st.nextToken();
				for (String cookieName : cookieNames)
					if (token.contains(cookieName)) {
						buf.append(token);
						buf.append(";");
						break;
					}					
			}
		}
		String ret = buf.toString();
		return ret.substring(0, ret.length()-1);
	}

	private static JSONParser parser = new JSONParser();
	private static JSONObject checkResponse(ClientResponse response, String message, String content) throws Exception {
        String output = response.getEntity(String.class);        
        JSONObject json = null; 
        if (output != null && output.length() > 0) {
        	json = (JSONObject)(parser.parse(output));
        
	        if (json.containsKey("code")) {
	        	Object codeObj = json.get("code");
	        	Long code = (long)0;
	        	if (codeObj instanceof String)
	        		code = Long.parseLong((String)codeObj);
	        	else if (codeObj instanceof Long)
	        		code = (Long)codeObj;
	        	if (!(code == 461 || (doLogin && code == 401))) {
		        	log("Error " + message);
		        	if (content != null)
		        		log(content);
		        	log(output);
	        	}
	        	if (!(code == 461 && message.contains("listing")))
	        		throw new Exception(codeObj.toString());
	        }
        }
        
        return json;
	}

	private static JSONObject checkResponse(ClientResponse response, String message) throws Exception {
		return checkResponse(response, message, null);
	}

    private static long getValidPrice(long price)
    {
        int increment = 1000;
        if (price <= 1000)
        {
            increment = 50;
        }
        else if (price <= 10000)
        {
            increment = 100;
        }
        else if (price <= 50000)
        {
            increment = 250;
        }
        else if (price <= 100000)
        {
            increment = 500;
        }

        return price / increment * increment;
    }
}
